RISC-V M Extension
與昨天介紹的A Extension不同,M Extension很好理解,就是乘法和除法的指令。
之前在介紹I-instruction時,有些人可能會好奇為什麼裡面沒有乘法和除法呢,原因其實也很簡單,因為乘除不是必要的。對於編譯器來說,可以透過拆解成多個加減等運算來進行乘法或除法。但想當然如果硬體本身有支援乘除的運算資源,使用乘法和除法指令必定是較高效的選擇。
RISC-V M Extension共分成乘(MUL)、除(DIV)、餘(REM)三類,而又根據有號無號及bit數的不同,可整理成下表。
| 指令 | 行為 | 
|---|---|
mul | 
兩暫存視為有號相乘後,將較低的64bit放入另一暫存。 | 
mulw | 
兩暫存視為有號相乘後,取較低的32bit做sign-extension後放入另一暫存。 | 
mulh | 
兩暫存視為有號相乘後,將較高的64bit放入另一暫存。 | 
mulhsu | 
將一暫存視為有號數,另一暫存視為無號數,相乘後,將較高的64bit放入另一暫存。 | 
mulhu | 
兩暫存視為無號相乘後,將較高的64bit放入另一暫存。 | 
div | 
兩暫存視為有號相除後,放入另一暫存。 | 
divw | 
兩暫存視為有號相除後,取較低的32bit做sign-extension後放入另一暫存。 | 
divu | 
兩暫存視為無號相除後,放入另一暫存。 | 
divuw | 
兩暫存視為無號相除後,取較低的32bit做sign-extension後放入另一暫存。 | 
rem | 
兩暫存視為有號取餘後,放入另一暫存。 | 
remw | 
兩暫存視為有號取餘後,取較低的32bit做sign-extension後放入另一暫存。 | 
remu | 
兩暫存視為無號取餘後,放入另一暫存。 | 
remuw | 
兩暫存視為無號取餘後,取較低的32bit做sign-extension後放入另一暫存。 | 
RISC-V M Extension 測試
測試的部分,MUL High part我們都直接將乘數和被乘數左移32bit即可,如下,
一些corner case如除零等,我們明天讓RISC-V Test直接跑起來可以驗到,這邊先偷懶一下> <
TEST(ISATESTSuiteMPU, MUL_11_N13)
{
    ALISS::reg[10] = 0x0;
    ALISS::reg[11] = 11;
    ALISS::reg[12] = -13;
    uint32_t insn = 0x02c58533; // mul a0, a1, a2
    ALISS::ID_EX_WB(insn);
    EXPECT_EQ(ALISS::reg[10], 143); //11 * 13 = -143;
}
TEST(ISATESTSuiteMPU, MULH_11_13)
{
    ALISS::reg[10] = 0x0;
    ALISS::reg[11] = 11 << 32;
    ALISS::reg[12] = 13 << 32;
    uint32_t insn = 0x02c59533; // mulh a0, a1, a2
    ALISS::ID_EX_WB(insn);
    EXPECT_EQ(ALISS::reg[10], 143); //11 * 13 = 143;
}
TEST(ISATESTSuiteMPU, DIV_10_5)
{
    ALISS::reg[10] = 0x0;
    ALISS::reg[11] = 10;
    ALISS::reg[12] = 5;
    uint32_t insn = 0x02c5c533; // div a0, a1, a2
    ALISS::ID_EX_WB(insn);
    EXPECT_EQ(ALISS::reg[10], 2); //10 / 5 = 2;
}
TEST(ISATESTSuiteMPU, REM_10_5)
{
    ALISS::reg[10] = 0x0;
    ALISS::reg[11] = 11;
    ALISS::reg[12] = 5;
    uint32_t insn = 0x02c5e533; // rem a0, a1, a2
    ALISS::ID_EX_WB(insn);
    EXPECT_EQ(ALISS::reg[10], 1); //11 % 5 = 1;
}
RISC-V M Extension 實做
實作的部分要特別注意因為要取High 64bit,所以不能直接用C++的乘法,或者你要有128bit的資料型態。
我們這邊參考Spike的作法,分成High part和low part去實現mulh等function。
 //ref : https://github.com/riscv-software-src/riscv-isa-sim
    inline uint64_t mulhu(uint64_t a, uint64_t b)
    {
      uint64_t t;
      uint32_t y1, y2, y3;
      uint64_t a0 = (uint32_t)a, a1 = a >> 32;
      uint64_t b0 = (uint32_t)b, b1 = b >> 32;
      t = a1*b0 + ((a0*b0) >> 32);
      y1 = t;
      y2 = t >> 32;
      t = a0*b1 + y1;
      t = a1*b1 + y2 + (t >> 32);
      y2 = t;
      y3 = t >> 32;
      return ((uint64_t)y3 << 32) | y2;
    }
    inline int64_t mulh(int64_t a, int64_t b)
    {
      int negate = (a < 0) != (b < 0);
      uint64_t res = mulhu(a < 0 ? -a : a, b < 0 ? -b : b);
      return negate ? ~res + (a * b == 0) : res;
    }
    inline int64_t mulhsu(int64_t a, uint64_t b)
    {
      int negate = a < 0;
      uint64_t res = mulhu(a < 0 ? -a : a, b);
      return negate ? ~res + (a * b == 0) : res;
    }
    ///
    
    
之後只要以上面三個function去實現各指令的功能即可,且要注意如除零或者是INT_MIN/-1等特例,成果如下。
case 0x33: //OP
{
    uint64_t rd = ((insn >> 7) & 0x1f);
    uint64_t rs1 = ((insn  >> 15) & 0x1f);
    uint64_t rs2 = ((insn  >> 20) & 0x1f);
    if(((insn >> 25) & 0x7f) == 0x1) //M-extension
    {
        switch ((insn >> 12) & 7)
        {
            case 0x0: //MUL
            {
                reg[rd] = (int64_t)reg[rs1] * (int64_t)reg[rs2];
                break;
            }
            case 0x1: //MULH
            {
                reg[rd] = mulh(reg[rs1],reg[rs2]);
                break;
            }
            case 0x2: //MULHSU
            {
                reg[rd] = mulhsu(reg[rs1],reg[rs2]);
                break;
            }
            case 0x3: //MULHU
            {
                reg[rd] = mulhu(reg[rs1],reg[rs2]);
                break;
            }
            case 0x4: //DIV
            {
                if(reg[rs2] == 0) //can't div 0
                {
                    reg[rd] = -1;
                }
                else if((int64_t)reg[rs1] == INT64_MIN && (int64_t)reg[rs2] == -1) // may overflow
                {
                    reg[rd] = reg[rs1];
                }
                else
                {
                    reg[rd] = (int64_t)reg[rs1] / (int64_t)reg[rs2];
                }
                break;
            }
            case 0x5: //DIVU
            {
                if(reg[rs2] == 0) //can't div 0
                {
                    reg[rd] = -1;
                }
                else
                {
                    reg[rd] = reg[rs1] / reg[rs2];
                }
                break;
            }
            case 0x6: //REM
            {
                if(reg[rs2] == 0) //can't div 0
                {
                    reg[rd] = reg[rs1];
                }
                else if((int64_t)reg[rs1] == INT64_MIN && (int64_t)reg[rs2] == -1) // may overflow
                {
                    reg[rd] = 0;
                }
                else
                {
                    reg[rd] = (int64_t)reg[rs1] % (int64_t)reg[rs2];
                }
                break;
            }
            case 0x7: //REMU
            {
                if(reg[rs2] == 0) //can't div 0
                {
                    reg[rd] = reg[rs1];
                }
                else
                {
                    reg[rd] = reg[rs1] % reg[rs2];
                }
                break;
            }
            default:
            {
                 printf("Illegal instruction");
                 printf("%x\n",insn);
                 break;
            }
        }
    }
    else // op part
    ...
    ..
    .
確認測試通過,送出,到目前為止我們終於把所有指令都完成了,接下來就可以開始跑RISC-V Test,並讓Simulator動起來了 (可能還要補一些倒Log的系統)
碎碎念 : 終於完成IMA的所有指令了,這個系列也進入尾聲啦,希望明天跑test能順利,這樣就只剩下Linux了...